using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
using XmlObjects.XmlWriter;

namespace XmlObjects.Parsers
{
	public abstract class Parser
	{
		#region static members

		private static readonly Dictionary<Type, Type> parsers = new Dictionary<Type, Type>();

		static Parser()
		{
			parsers.Add(typeof (int), typeof (Parseable<int>));
			parsers.Add(typeof (long), typeof (Parseable<long>));
			parsers.Add(typeof (float), typeof (Parseable<float>));
			parsers.Add(typeof (double), typeof (Parseable<double>));
			parsers.Add(typeof (decimal), typeof (Parseable<decimal>));
			parsers.Add(typeof (DateTime), typeof (Parseable<DateTime>));
			parsers.Add(typeof (FileInfo), typeof (StringConstructable<FileInfo>));
			parsers.Add(typeof (DirectoryInfo), typeof (StringConstructable<DirectoryInfo>));
			parsers.Add(typeof (string), typeof (StringParser<string>));
			parsers.Add(typeof (List<>), typeof (ListParser<>));
			parsers.Add(typeof (IList<>), typeof (ListParser<>));
		}

		public static IXmlParser GetParser(PropertyInfo propertyInfo)
		{
			ElementToPropertyMap map = new ElementToPropertyMap(propertyInfo);
			
			Parser p = GetParserObject(propertyInfo.PropertyType);
			PropertyAttribute attribute = (PropertyAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyAttribute));
			p.SelectString = attribute.SelectString;
			p.Map = map;
			p.QueryCreateNode = attribute.QueryCreateNode;
			return (IXmlParser) p;
		}

		private static Parser GetParserObject(Type typeToParse)
		{
			Type parserType = null;
			if (parsers.ContainsKey(typeToParse))
			{
				parserType = parsers[typeToParse];
			}
			else
			{
				if (typeToParse.IsSubclassOf(typeof (object))) // && typeToParse.GetConstructor(new Type[] {}) != null)
				{
					if (typeToParse.IsGenericType)
					{
						//  Look for the generic type in the parser cache.
						Type typeToParseGenericTypeDef = typeToParse.GetGenericTypeDefinition();
						if (parsers.ContainsKey(typeToParseGenericTypeDef))
						{
							Type parserGenericTypeDef = parsers[typeToParseGenericTypeDef];
							parserType = parserGenericTypeDef.MakeGenericType(typeToParse.GetGenericArguments());
						}
					}
					else if (typeToParse.IsArray)
					{
						parserType = typeof (ArrayParser<>).MakeGenericType(typeToParse.GetElementType());
					}

					if (parserType==null)
						parserType = typeof (Hydrator<>).MakeGenericType(typeToParse);
					parsers.Add(typeToParse, parserType);
				}
				else
				{
					throw new UnableToParseException(typeToParse);
				}
			}

			try
			{
				return (Parser) Activator.CreateInstance(parserType);
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex.Message);
				throw;
			}
		}

		public static Parser<T> GetLocalNodeParser<T>()
		{
			return (Parser<T>) GetParserObject(typeof (T));
		}

		#endregion

		#region Instance Members

		public Parser()
		{
			SelectString = ".";
		}

		private string selectString;

		public QueryCreateNode QueryCreateNode;
		protected Type defaultSubType = null;
		private string findSubtypeBy = "#name";
		private ElementToPropertyMap map;

		public string SelectString
		{
			get { return selectString; }
			set { selectString = value; }
		}

		public string FindSubtypeBy
		{
			get { return findSubtypeBy; }
			set { findSubtypeBy = value; }
		}

		internal ElementToPropertyMap Map
		{
			get { return map; }
			set { map = value; }
		}

		protected bool HasSubTypes()
		{
			return Map!= null && Map.SubTypeMap.Count > 0;
		}

		#endregion
	}


	public abstract class Parser<T> : Parser, IXmlParser<T>, ILocalNodeParser<T>
	{
		public abstract T GetValue(XmlNode node);

		object IXmlParser.GetValue(XmlNode node)
		{
			return ((IXmlParser<T>) this).GetValue(node);
		}

		public abstract T GetValueLocal(XmlNode node);

		/// <summary>
		/// For internal use.  Checks whether the node is null and if so returns null.
		/// Else returns innerXml of the node.
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		protected string GetInnerXml(XmlNode node)
		{
			if (node == null)
				return null;
			else
				return node.InnerXml;
		}
	}
}